#include "listenthread.h"
#include <QByteArray>
#include <QJsonArray>
#include <QJsonDocument>
#include <QRegularExpression>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QSqlError>
#include <QUrl>
#include <QVariant>
listenThread::listenThread(QObject *parent)
	: QThread(parent)
	, m_threadDBName(QString("TDB%1").arg((quint64(this))))

{
	//注册方法
	m_functions["help"] = std::bind(&listenThread::func_help,this,std::placeholders::_1,std::placeholders::_2);
	m_functions["altitude"] = std::bind(&listenThread::func_altitude,this,std::placeholders::_1,std::placeholders::_2);
	m_functions["object_by_pos"] = std::bind(&listenThread::func_object_by_pos,this,std::placeholders::_1,std::placeholders::_2);
	m_functions["object_by_name"] = std::bind(&listenThread::func_object_by_name,this,std::placeholders::_1,std::placeholders::_2);
	//m_functions["geo_by_osmid"] = std::bind(&listenThread::func_geo_by_osmid,this,std::placeholders::_1,std::placeholders::_2);
}

void listenThread::run()
{
	//connect to database
	QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL",m_threadDBName+"_gis");
	if (db.isValid()==false)
		return;
	db.setHostName("127.0.0.1");
	db.setPort(5432);
	db.setUserName("archosm");
	db.setPassword("archosm");
	db.setDatabaseName("gis");
	if (db.open()==false)
		return;

	db = QSqlDatabase::addDatabase("QPSQL",m_threadDBName+"_contours");
	if (db.isValid()==false)
		return;
	db.setHostName("127.0.0.1");
	db.setPort(5432);
	db.setUserName("archosm");
	db.setPassword("archosm");
	db.setDatabaseName("contours");
	if (db.open()==false)
		return;


	FCGX_Request request;
	FCGX_InitRequest(&request, 0, 0);
	int rc = FCGX_Accept_r(&request);
	while (rc >=0)
	{
		deal_client(&request);
		FCGX_Finish_r(&request);
		rc = FCGX_Accept_r(&request);
	}

	QSqlDatabase::removeDatabase(m_threadDBName+"_gis");
	QSqlDatabase::removeDatabase(m_threadDBName+"_contours");

	quit();
}

void listenThread::deal_client(FCGX_Request * request)
{
	QHash < QString, QString> query_paras;
	const char * const query_string=FCGX_GetParam("QUERY_STRING",request->envp);
	QString str = QString::fromUtf8(query_string) ;
	QStringList lst = str.split("&",Qt::SkipEmptyParts);
	//form paramenter list
	foreach (QString pai, lst)
	{
		int pd = pai.indexOf("=");
		if (pd>0 && pd < pai.length())
		{
			QString key = pai.left(pd);
			QString v = pai.mid(pd+1);
			query_paras[key.trimmed()]  = v;
		}
	}
	//Get paras
	//是否缩进Json
	const bool bJsonIndented = query_paras["indented"].toInt()?true:false;
	//功能
	const QString functionStr = query_paras["function"];

	//form root json object_by_pos
	QJsonObject root;

	//根据功能继续操作
	if (m_functions.contains(functionStr))
		m_functions[functionStr](query_paras,root);
	else
		func_help(query_paras,root);




	//output to text
	QJsonDocument doc(root);
	QByteArray arrJson = doc.toJson(bJsonIndented?QJsonDocument::Indented:QJsonDocument::Compact);
	//Output
	FCGX_PutS("Content-type: text/plain; charset=UTF-8\n\n",request->out);
	FCGX_PutStr(arrJson.constData(),arrJson.length(),request->out);
}
void listenThread::func_help(const QHash < QString, QString> query_paras, QJsonObject & jsonObj )
{
	foreach (QString s, query_paras.keys())
		jsonObj[s] = query_paras[s];
	jsonObj["usage"] = QString().asprintf("Usage:\n"
							  "funcion=[altitude object_by_pos object_by_name]\n"
							  "\taltitude: lat, lon\n"
							  "\tobject_by_pos: lat, lon\n"
							  "\tobject_by_name: name\n"
							  "indented = [0 1]"
							  );

}

void listenThread::func_altitude(const QHash < QString, QString> query_paras, QJsonObject & jsonObj )
{
	foreach (QString s, query_paras.keys())
		jsonObj[s] = query_paras[s];
	jsonObj["result"] = "error";
	if (query_paras.contains("lat")==false)
	{
		jsonObj["reason"] = "need lat element.";
		return;
	}
	if (query_paras.contains("lon")==false)
	{
		jsonObj["reason"] = "need lon element.";
		return;
	}

	double lon = query_paras["lon"].toDouble();
	double lat = query_paras["lat"].toDouble();
	while (lon<-180) lon += 360;
	while (lon>180) lon -= 360;
	if (lat<-85 || lat >85)
	{
		jsonObj["reason"] = "lat must between -85 and 85 degree.";
		return;
	}
	QSqlDatabase db = QSqlDatabase::database(m_threadDBName+"_contours");
	if (db.isOpen()==false)
	{
		jsonObj["reason"] = "Database connection is not ok.";
		jsonObj["error_msg"] = db.lastError().text();
		return;
	}

	QSqlQuery query(db);
	query.prepare("SELECT planet_osm_line.ele,"
				  "planet_osm_line.way <-> ST_TRANSFORM(ST_SetSRID(ST_MakePoint(?,?),4326),3857) as distance"
				  " FROM public.planet_osm_line "
				  " ORDER BY planet_osm_line.way <-> ST_TRANSFORM(ST_SetSRID(ST_MakePoint(?,?),4326),3857)  LIMIT 1;"
				  );
	query.addBindValue(lon);
	query.addBindValue(lat);
	query.addBindValue(lon);
	query.addBindValue(lat);

	if (query.exec()==false)
	{
		jsonObj["reason"] = "database query error.";
		jsonObj["error_msg"] = query.lastError().text();
		return;
	}
	if (query.next()==false)
	{
		jsonObj["reason"] = "database query empty.";
		return;
	}

	double distance =  query.value(1).toDouble();

	jsonObj["result"] = "succeeded";
	jsonObj["altitude"] = query.value(0).toInt();
	jsonObj["distance"] = distance;

	if (distance>500)
	{
		query.prepare("SELECT contour.ele,"
					  "contour.way <-> ST_TRANSFORM(ST_SetSRID(ST_MakePoint(?,?),4326),3857) as distance"
					  " FROM public.contour "
					  " ORDER BY contour.way <-> ST_TRANSFORM(ST_SetSRID(ST_MakePoint(?,?),4326),3857)  LIMIT 1;"
					  );
		query.addBindValue(lon);
		query.addBindValue(lat);
		query.addBindValue(lon);
		query.addBindValue(lat);

		if (query.exec()==false)
		{
			jsonObj["reason"] = "database query error.";
			jsonObj["error_msg"] = query.lastError().text();
			return;
		}
		if (query.next()==false)
		{
			jsonObj["reason"] = "database query empty.";
			return;
		}

		distance =  query.value(1).toDouble();

		jsonObj["result"] = "succeeded";
		jsonObj["altitude"] = query.value(0).toInt();
		jsonObj["distance"] = distance;

	}
	if (distance>2000)
	{
		jsonObj["info"] = "Warning, distance too far, ele value may be error.";
	}

}

void listenThread::func_object_by_pos(const QHash < QString, QString> query_paras, QJsonObject & jsonObj )
{
	jsonObj["result"] = "error";
	foreach (QString s, query_paras.keys())
		jsonObj[s] = query_paras[s];
	if (query_paras.contains("lat")==false)
	{
		jsonObj["reason"] = "need lat element.";
		return;
	}
	if (query_paras.contains("lon")==false)
	{
		jsonObj["reason"] = "need lon element.";
		return;
	}

	double lon = query_paras["lon"].toDouble();
	double lat = query_paras["lat"].toDouble();
	while (lon<-180) lon += 360;
	while (lon>180) lon -= 360;
	if (lat<-85 || lat >85)
	{
		jsonObj["reason"] = "lat must between -85 and 85 degree.";
		return;
	}
	QSqlDatabase db = QSqlDatabase::database(m_threadDBName+"_gis");
	if (db.isOpen()==false)
	{
		jsonObj["reason"] = "Database connection is not ok.";
		jsonObj["error_msg"] = db.lastError().text();
		return;
	}

	QSqlQuery query(db);
	query.prepare("select * from("
				  "select  osm_id, name, trans_name_chs, ST_GeometryType(way) as geotype, ST_AsText(ST_Transform(ST_Centroid(way),4326)) as center_pos, ST_Distance(ST_Centroid(ST_Transform(ST_SetSRID(ST_MakePoint(?,?),4326),3857)),way) as distance from planet_osm_line where ST_Covers(ST_Transform(ST_MakeEnvelope(?,?,?,?,4326),3857), way) and (trans_name_chs  > ' ' or name >' ')"
				  " union "
				  "select  osm_id, name, trans_name_chs, ST_GeometryType(way) as geotype, ST_AsText(ST_Transform(ST_Centroid(way),4326)) as center_pos, ST_Distance(ST_Centroid(ST_Transform(ST_SetSRID(ST_MakePoint(?,?),4326),3857)),way) as distance from planet_osm_point where ST_Covers(ST_Transform(ST_MakeEnvelope(?,?,?,?,4326),3857), way) and (trans_name_chs  > ' ' or name >' ')"
				  " union "
				  " select  osm_id, name, trans_name_chs, ST_GeometryType(way) as geotype, ST_AsText(ST_Transform(ST_Centroid(way),4326)) as center_pos, ST_Distance(ST_Centroid(ST_Transform(ST_SetSRID(ST_MakePoint(?,?),4326),3857)),way) as distance from planet_osm_roads where ST_Covers(ST_Transform(ST_MakeEnvelope(?,?,?,?,4326),3857), way) and (trans_name_chs  > ' ' or name >' ')"
				  " union "
				  " select  osm_id, name, trans_name_chs, ST_GeometryType(way) as geotype, ST_AsText(ST_Transform(ST_Centroid(way),4326)) as center_pos, ST_Distance(ST_Centroid(ST_Transform(ST_SetSRID(ST_MakePoint(?,?),4326),3857)),way) as distance from planet_osm_polygon where ST_Covers(ST_Transform(ST_MakeEnvelope(?,?,?,?,4326),3857), way) and (trans_name_chs  > ' ' or name >' ')"
				  ") query_object order by distance ;"
				  );
	query.addBindValue(lon);	query.addBindValue(lat);
	query.addBindValue(lon-0.01);	query.addBindValue(lat-0.01);
	query.addBindValue(lon+0.01);	query.addBindValue(lat+0.01);
	query.addBindValue(lon);	query.addBindValue(lat);
	query.addBindValue(lon-0.01);	query.addBindValue(lat-0.01);
	query.addBindValue(lon+0.01);	query.addBindValue(lat+0.01);
	query.addBindValue(lon);	query.addBindValue(lat);
	query.addBindValue(lon-0.01);	query.addBindValue(lat-0.01);
	query.addBindValue(lon+0.01);	query.addBindValue(lat+0.01);
	query.addBindValue(lon);	query.addBindValue(lat);
	query.addBindValue(lon-0.01);	query.addBindValue(lat-0.01);
	query.addBindValue(lon+0.01);	query.addBindValue(lat+0.01);

	if (query.exec()==false)
	{
		jsonObj["reason"] = "database query error.";
		jsonObj["error_msg"] = query.lastError().text();
		return;
	}
	int nItems = 0;
	while (query.next())
	{
		QJsonObject objitem;
		int cols = query.record().count();
		for (int i=0;i<cols;++i)
			objitem[query.record().fieldName(i)] = query.value(i).toString();
		jsonObj[QString("result%1").arg(nItems)] = objitem;
		++nItems;
	}

	jsonObj["items"] = nItems;
	jsonObj["result"] = "succeeded";

}

void listenThread::func_object_by_name(const QHash < QString, QString> query_paras, QJsonObject & jsonObj )
{
	jsonObj["result"] = "error";
	foreach (QString s, query_paras.keys())
		jsonObj[s] = query_paras[s];
	if (query_paras.contains("name")==false)
	{
		jsonObj["reason"] = "need name element.";
		return;
	}
	QString rawnamestr = jsonObj["name"].toString();
	jsonObj["raw_name"] = rawnamestr;
	QUrl url(rawnamestr);
	QString namestring = url.toDisplayString();
	namestring.remove(QRegularExpression("[\\pP‘’“”,\\+\\-()[\\]\\^%~`\\!]"));
	namestring = namestring.trimmed();

	jsonObj["name"] = namestring;

	if (namestring.length()<2)
	{
		jsonObj["reason"] = "name must contain more than 1 characters.";
		return;

	}

	QSqlDatabase db = QSqlDatabase::database(m_threadDBName+"_gis");
	if (db.isOpen()==false)
	{
		jsonObj["reason"] = "Database connection is not ok.";
		jsonObj["error_msg"] = db.lastError().text();
		return;
	}

	QSqlQuery query(db);
	QString str = QString(
				  "select  osm_id, name, trans_name_chs, ST_GeometryType(way) as geotype, ST_AsText(ST_Transform(ST_Centroid(way),4326)) as center_pos from planet_osm_line where trans_name_chs  like '%1%%' or name like '%1%%' or raw_name like '%1%%'   "
				  " union "
				  "select  osm_id, name, trans_name_chs, ST_GeometryType(way) as geotype, ST_AsText(ST_Transform(ST_Centroid(way),4326)) as center_pos from planet_osm_point where trans_name_chs  like '%1%%'  or name like '%1%%' or raw_name like '%1%%'  "
				  " union "
				  " select  osm_id, name, trans_name_chs, ST_GeometryType(way) as geotype, ST_AsText(ST_Transform(ST_Centroid(way),4326)) as center_pos from planet_osm_roads where trans_name_chs  like '%1%%'  or name like '%1%%' or raw_name like '%1%%'  "
				  " union "
				  " select  osm_id, name, trans_name_chs, ST_GeometryType(way) as geotype, ST_AsText(ST_Transform(ST_Centroid(way),4326)) as center_pos from planet_osm_polygon where trans_name_chs  like '%1%%'  or name like '%1%%' or raw_name like '%1%%'  "
				  ).arg(namestring);
	if (query.exec(str)==false)
	{
		jsonObj["reason"] = "database query error.";
		jsonObj["error_msg"] = query.lastError().text();
		return;
	}
	int nItems = 0;
	while (query.next())
	{
		QJsonObject objitem;
		int cols = query.record().count();
		for (int i=0;i<cols;++i)
			objitem[query.record().fieldName(i)] = query.value(i).toString();
		jsonObj[QString("result%1").arg(nItems)] = objitem;
		++nItems;
	}

	jsonObj["items"] = nItems;
	jsonObj["result"] = "succeeded";

}
void listenThread::func_geo_by_osmid(const QHash < QString, QString> query_paras, QJsonObject & jsonObj )
{
	jsonObj["result"] = "error";
	foreach (QString s, query_paras.keys())
		jsonObj[s] = query_paras[s];
	if (query_paras.contains("osm_id")==false)
	{
		jsonObj["reason"] = "need osm_id element.";
		return;
	}
	qint64 osm_id = jsonObj["osm_id"].toString().toLongLong();


	QSqlDatabase db = QSqlDatabase::database(m_threadDBName+"_gis");
	if (db.isOpen()==false)
	{
		jsonObj["reason"] = "Database connection is not ok.";
		jsonObj["error_msg"] = db.lastError().text();
		return;
	}

	QSqlQuery query(db);
	QString str = QString(
				  "select  osm_id, name, trans_name_chs, ST_GeometryType(way) as geotype, ST_AsText(ST_Transform(way,4326)) as geo_object from planet_osm_line where osm_id =  %1 "
				  " union "
				  "select  osm_id, name, trans_name_chs, ST_GeometryType(way) as geotype, ST_AsText(ST_Transform(way,4326)) as geo_object from planet_osm_point where osm_id =  %1 "
				  " union "
				  " select  osm_id, name, trans_name_chs, ST_GeometryType(way) as geotype, ST_AsText(ST_Transform(way,4326)) as geo_object from planet_osm_roads where osm_id =  %1  "
				  " union "
				  " select  osm_id, name, trans_name_chs, ST_GeometryType(way) as geotype, ST_AsText(ST_Transform(way,4326)) as geo_object from planet_osm_polygon where osm_id =  %1 "
				  ).arg(osm_id);
	if (query.exec(str)==false)
	{
		jsonObj["reason"] = "database query error.";
		jsonObj["error_msg"] = query.lastError().text();
		return;
	}
	int nItems = 0;
	while (query.next())
	{
		QJsonObject objitem;
		int cols = query.record().count();
		for (int i=0;i<cols;++i)
			objitem[query.record().fieldName(i)] = query.value(i).toString();
		jsonObj[QString("result%1").arg(nItems)] = objitem;
		++nItems;
	}

	jsonObj["items"] = nItems;
	jsonObj["result"] = "succeeded";

}
